package gagak.framework.web.paramresovler.requestbody;

import gagak.framework.web.exception.HttpMediaTypeNotSupportedException;
import gagak.framework.web.exception.HttpMessageNotReadableException;
import gagak.framework.web.exception.InvalidMediaTypeException;
import gagak.framework.web.paramresovler.*;
import gagak.framework.web.paramresovler.converter.GenericHttpMessageConverter;
import gagak.framework.web.support.http.HttpHeaders;
import gagak.framework.web.support.http.MediaType;
import gagak.framework.web.annonation.HttpMethod;
import gagak.framework.web.param.MethodParameter;
import gagak.framework.web.reflect.ResolvableType;
import gagak.framework.web.support.request.NativeWebRequest;
import gagak.framework.web.utils.Assert;
import gagak.framework.web.utils.StreamUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;
import java.lang.reflect.Type;
import java.util.*;

/*
 * @program: ce
 * @description: 解析@Requestbody这种参数数据类型 这里将http信息处理和包装
 * @author: houhong
 * @create: 2022-11-25 22:03
 */
public abstract class AbstractMessageConverterMethodArgumentResolver implements HandlerMethodArgumentResolver {

    protected final Logger logger = LoggerFactory.getLogger(getClass());

    /*
     * 支持的数据类型
     */
    private static final Set<HttpMethod> SUPPORTED_METHODS =
            EnumSet.of(HttpMethod.POST, HttpMethod.PUT, HttpMethod.PATCH);


    private static final Object NO_VALUE = new Object();

    /*
     * 消息解析器
     */
    public List<HttpMessageConverter<?>> httpMessageConverters;


    /*
     * 允许支持的媒体类型
     */
    protected List<MediaType> allSupportedMediaTypes;

    public AbstractMessageConverterMethodArgumentResolver(List<HttpMessageConverter<?>> converters) {
        Assert.notEmpty(converters, "'messageConverters' must not be empty");
        this.httpMessageConverters = converters;

        // 它会把所有的消息转换器里支持的MediaType都全部拿出来汇聚起来~
        this.allSupportedMediaTypes = getAllSupportedMediaTypes(converters);
    }


    private static List<MediaType> getAllSupportedMediaTypes(List<HttpMessageConverter<?>> messageConverters) {
        /*
         *  有序性
         *
         */
        Set<MediaType> allSupportedMediaTypes = new LinkedHashSet<MediaType>();
        for (HttpMessageConverter<?> messageConverter : messageConverters) {
            allSupportedMediaTypes.addAll(messageConverter.getSupportedMediaTypes());
        }
        List<MediaType> result = new ArrayList<MediaType>(allSupportedMediaTypes);
        MediaType.sortBySpecificity(result);
        return Collections.unmodifiableList(result);
    }


    /*
     * 利用消息转换器，将HttpRequest 的数据j解析出来
     */
    protected <T> Object readWithMessageConverters(NativeWebRequest request, MethodParameter parameter,
                                                   Type paramType) throws IOException, HttpMediaTypeNotSupportedException {

        HttpInputMessage inputMessage = createInputMessage(request);
        return readWithMessageConverters(inputMessage, parameter, paramType);
    }


    /*
     * client 到server 进行的 解析http协议，消息转换 ，获取更多信息在框架里使用
     */
    protected ServletServerHttpRequest createInputMessage(NativeWebRequest webRequest) {
        HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
        Assert.state(servletRequest != null, "No HttpServletRequest");
        return new ServletServerHttpRequest(servletRequest);
    }

    /*
    *  使用消息转换器将httpRquestBody的信息读取出来
     *  1:  解析metaType，获取
     *     contentType:然后根据contentype
     *     策略的获取对应的解析器，然后解析
     *
     *
    */
    protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
                                                   Type targetType) throws IOException, HttpMediaTypeNotSupportedException {

        MediaType contentType;

        boolean noContentType = false;
        try {
            contentType = inputMessage.getHeaders().getContentType();
        } catch (InvalidMediaTypeException ex) {
            throw new HttpMediaTypeNotSupportedException(ex.getMessage());
        }
        if (contentType == null) {
            noContentType = true;
            /*
             *    "application/octet-stream";
             */
            contentType = MediaType.APPLICATION_OCTET_STREAM;
        }


        Class<?> contextClass = parameter.getContainingClass();

        Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);
        if (targetClass == null) {

            ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
            targetClass = (Class<T>) resolvableType.resolve();
        }

        HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null);
        Object body = NO_VALUE;

        EmptyBodyCheckingHttpInputMessage message;
        try {
            message = new EmptyBodyCheckingHttpInputMessage(inputMessage);

            for (HttpMessageConverter<?> converter : this.httpMessageConverters) {

                Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();

                GenericHttpMessageConverter<?> genericConverter =
                        (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);

                if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
                        (targetClass != null && converter.canRead(targetClass, contentType))) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Read [" + targetType + "] as \"" + contentType + "\" with [" + converter + "]");
                    }

                    if (message.hasBody()) {
                      /*  HttpInputMessage msgToUse =
                                getAdvice().beforeBodyRead(message, parameter, targetType, converterType);*/
                        body = ( genericConverter != null ? genericConverter.read(targetType, contextClass, message) :
                                ((HttpMessageConverter<T>) converter).read(targetClass, message));
                        /*
                         *  在转换器处理完之后再处理
                         */
                        //body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
                    } else {
                        // body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
                    }
                    break;
                }
            }
        } catch (IOException ex) {
            throw new HttpMessageNotReadableException("I/O error while reading input message", ex);
        }

        if (body == NO_VALUE) {
            if (httpMethod == null || !SUPPORTED_METHODS.contains(httpMethod) ||
                    (noContentType && !message.hasBody())) {
                return null;
            }
            throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes);
        }

        return body;
    }

    private static class EmptyBodyCheckingHttpInputMessage implements HttpInputMessage {

        private final HttpHeaders headers;

        private final InputStream body;

        public EmptyBodyCheckingHttpInputMessage(HttpInputMessage inputMessage) throws IOException {
            this.headers = inputMessage.getHeaders();
            InputStream inputStream = inputMessage.getBody();
            if (inputStream.markSupported()) {
                inputStream.mark(1);
                this.body = (inputStream.read() != -1 ? inputStream : null);
                inputStream.reset();
            } else {
                PushbackInputStream pushbackInputStream = new PushbackInputStream(inputStream);
                int b = pushbackInputStream.read();
                if (b == -1) {
                    this.body = null;
                } else {
                    this.body = pushbackInputStream;
                    pushbackInputStream.unread(b);
                }
            }
        }

        @Override
        public HttpHeaders getHeaders() {
            return this.headers;
        }

        @Override
        public InputStream getBody() {
            return (this.body != null ? this.body : StreamUtils.emptyInput());
        }

        public boolean hasBody() {
            return (this.body != null);
        }
    }

}